package server;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.SocketException;

import message.Request;
import message.RequestWrapper;
import message.Response;
import message.ResponseWrapper;
import message.request.DownloadFileRequest;
import message.request.InfoRequest;
import message.request.ListRequest;
import message.request.UploadRequest;
import message.request.VersionRequest;
import message.response.CorruptedMessageResponse;
import message.response.DownloadFileResponse;
import message.response.InfoResponse;
import message.response.ListResponse;
import message.response.MessageResponse;
import message.response.VersionResponse;
import util.ChecksumUtils;
import util.Config;
import util.IntegrityTool;

public class ClientWorker extends Thread implements IFileServer {

	private static int counter = 0;

	private FileServer fs;
	private Socket socket;

	//how often a message gets corrupted (only for testing)
	//private static int corruptCounter = 0;
	
	private ObjectOutputStream out;
	private ObjectInputStream in;

	private Config messages;

	private IntegrityTool integrityTool;

	public ClientWorker(Socket socket, FileServer fs) {
		super("ClientWorkerThread-" + ++counter);

		this.socket = socket;
		this.fs = fs;
		messages = new Config("messages");

		try {
			integrityTool = new IntegrityTool(fs.getConfig());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
		try {

			out = new ObjectOutputStream(socket.getOutputStream());
			in = new ObjectInputStream(socket.getInputStream());

			Object requestObj = in.readObject();
			Object responseObj = null;

			boolean proxyCommunication = false;
			Request request = null;
			Response response = null;

			boolean integrityVerified = true;
			/*
			 * Check if request has a HMAC. The only request without a HMAC is a
			 * download direct from the client. In this case we do not check the
			 * integrity of the communication and instead of the RequestWrappers
			 * we get the original DTO's.
			 */
			if (requestObj instanceof RequestWrapper) {
				proxyCommunication = true;
				integrityVerified = false;
				RequestWrapper requestW = (RequestWrapper) requestObj;
				request = requestW.getRequest();
				
				//UploadRequests get corrupted (testing only)
				//if(request instanceof UploadRequest && corruptCounter < 1) {
				//	corruptCounter++;
				//	request = new VersionRequest("corrupt.txt");
				//}
				
				try {
					integrityVerified = integrityTool.verifyMac(
							request.toString(), requestW.getHmac());
				} catch (Exception e) {
					e.printStackTrace();
				}
				// First check if message integrity is verified.
				// If not return the corrupted message to the Proxy!
				if (!integrityVerified) {
					response = new CorruptedMessageResponse(requestW);
				}
			} else {
				request = (Request) requestObj;
			}

			//in case that the message was corrupted the response is already set.
			if (response == null) {
				
				if (request instanceof ListRequest) {

					response = list();

				} else if (request instanceof InfoRequest) {

					response = info((InfoRequest) request);

				} else if (request instanceof VersionRequest) {

					response = version((VersionRequest) request);

				} else if (request instanceof DownloadFileRequest) {

					response = download((DownloadFileRequest) request);

				} else if (request instanceof UploadRequest) {

					response = upload((UploadRequest) request);

				} else {
					response = new MessageResponse(
							messages.getString("error.unknownCommand"));
				}
			}

			if (proxyCommunication) {
				String hmac = "";

				try {
					hmac = integrityTool.createMac(response.toString());
				} catch (Exception e) {
					e.printStackTrace();
				}

				responseObj = new ResponseWrapper(response, hmac);
			} else {
				responseObj = response;
			}

			out.writeObject(responseObj);

		} catch (ClassNotFoundException e) {
			System.out.println(new MessageResponse(messages
					.getString("error.unexpected")));
		} catch (SocketException e) {
			// Ignore
		} catch (IOException e) {
			System.out.println(new MessageResponse(messages
					.getString("error.unexpected")));
		} finally {
			shutdown();
		}
	}

	public void shutdown() {

		try {

			if (socket != null && !socket.isClosed()) {
				socket.shutdownInput();
				socket.shutdownOutput();
				socket.close();
			}

			if (in != null)
				in.close();

			if (out != null)
				out.close();

			socket = null;
			in = null;
			out = null;

		} catch (IOException e) {
			System.out.println(new MessageResponse(messages
					.getString("error.unexpected")));
		}
	}

	@Override
	public Response list() throws IOException {

		if (fs.getFileList().size() == 0)
			return new MessageResponse(messages.getString("info.noFiles"));

		return new ListResponse(fs.getFileList());
	}

	@Override
	public Response info(InfoRequest request) throws IOException {

		long fileSize = 0;
		String fileName = request.getFilename();

		File file = new File(fs.getDirectory(), fileName);

		if (file.exists()) {
			fileSize = file.length();

			return new InfoResponse(fileName, fileSize);
		} else {
			return new MessageResponse(
					messages.getString("error.fileDoesNotExist") + "('"
							+ fileName + "')");
		}
	}

	@Override
	public Response version(VersionRequest request) throws IOException {
		String fileName = request.getFilename();

		File file = new File(fs.getDirectory(), fileName);

		// todo check if ok... list() updates versions
		if (file.exists()) {
			int version = (fs.getFileVersions().containsKey(fileName)) ? fs
					.getFileVersions().get(fileName) : 1;

			return new VersionResponse(fileName, version);
		} else {
			return new MessageResponse(
					messages.getString("error.fileDoesNotExist") + "('"
							+ fileName + "')");
		}
	}

	@Override
	public Response download(DownloadFileRequest request) throws IOException {

		String username = request.getTicket().getUsername();
		String checksum = request.getTicket().getChecksum();
		String fileName = request.getTicket().getFilename();

		File file = new File(fs.getDirectory(), fileName);

		if (!file.exists())
			return new MessageResponse(
					messages.getString("error.fileDoesNotExist") + "('"
							+ fileName + "')");

		Integer version = fs.getFileVersions().get(fileName);
		version = (version == null) ? 0 : version;

		boolean notCorrupt = ChecksumUtils.verifyChecksum(username, file,
				version, checksum);

		if (notCorrupt) {

			BufferedReader reader = new BufferedReader(new FileReader(file));

			String line = null;
			StringBuilder stringBuilder = new StringBuilder();
			String ls = System.getProperty("line.separator");

			while ((line = reader.readLine()) != null) {
				stringBuilder.append(line);
				stringBuilder.append(ls);
			}
			reader.close();

			return new DownloadFileResponse(request.getTicket(), stringBuilder
					.toString().getBytes());
		} else {
			return new MessageResponse(
					messages.getString("error.ticketCorrupt"));
		}

	}

	@Override
	public MessageResponse upload(UploadRequest request) throws IOException {
		int version = request.getVersion();
		String fileName = request.getFilename();
		String fileContent = new String(request.getContent());

		File file = new File(fs.getDirectory(), fileName);

		fs.getFileVersions().put(fileName, version);

		file.createNewFile();

		BufferedWriter out = new BufferedWriter(new FileWriter(file, false));

		out.write(fileContent);
		out.close();

		return new MessageResponse(messages.getString("info.upload"));
	}

}
